﻿/************************************************************************
 * Copyright (c) 2008, Jason Whitehorn (jason.whitehorn@gmail.com)
 * All rights reserved.
 * 
 * Source code and binaries distributed under the terms of the included
 * license, see license.txt for details.
 ************************************************************************/
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;

namespace ContractTest.Assertions {
    /// <summary>
    /// ContractTest's only implementation of ITypeAsserter.
    /// </summary>
    public class TypeAssert : ITypeAsserter {

        /// <see cref="ITypeAsserter"/>
        public void IsClass<T>() {
            TypeType type = GetTypeType(typeof (T));
            if(type != TypeType.Class)
                throw new ContractAssertionException(TypeType.Class, type);
        }

        /// <see cref="ITypeAsserter"/>
        public void IsEnum<T>() {
            TypeType type = GetTypeType(typeof(T));
            if (type != TypeType.Enumeration)
                throw new ContractAssertionException(TypeType.Enumeration, type);
        }

        /// <see cref="ITypeAsserter"/>
        public void IsInterface<T>() {
            TypeType type = GetTypeType(typeof(T));
            if (type != TypeType.Interface)
                throw new ContractAssertionException(TypeType.Interface, type);
        }

        /// <see cref="ITypeAsserter"/>
        public void HasDefaultConstructor<T>() {
            bool hasDefault = HasDefaultConstructor(typeof (T), VisibilityModifier.Internal, VisibilityModifier.Private, VisibilityModifier.Protected, VisibilityModifier.Public);
            if(!hasDefault)
                throw new ContractAssertionException("Expected default constructor, non present.");
        }

        /// <see cref="ITypeAsserter"/>
        public void LacksDefaultConstructor<T>() {
            bool hasDefault = HasDefaultConstructor(typeof(T), VisibilityModifier.Internal, VisibilityModifier.Private, VisibilityModifier.Protected, VisibilityModifier.Public);
            if (hasDefault)
                throw new ContractAssertionException("Expected no default constructor, one present.");
        }

        /// <see cref="ITypeAsserter"/>
        public void HasPublicDefaultConstructor<T>() {
            bool hasDefault = HasDefaultConstructor(typeof(T), VisibilityModifier.Public);
            if (!hasDefault)
                throw new ContractAssertionException("Expected default constructor, non present.");
        }

        /// <see cref="ITypeAsserter"/>
        public void HasPrivateDefaultConstructor<T>() {
            bool hasDefault = HasDefaultConstructor(typeof(T), VisibilityModifier.Private);
            if (!hasDefault)
                throw new ContractAssertionException("Expected default constructor, non present.");
        }

        /// <see cref="ITypeAsserter"/>
        public void LacksPublicDefaultConstructor<T>() {
            bool hasDefault = HasDefaultConstructor(typeof(T), VisibilityModifier.Public);
            if (hasDefault)
                throw new ContractAssertionException("Expected default constructor, non present.");
        }

        /// <see cref="ITypeAsserter"/>
        public void DefaultConstructorHasMaxVisibility<T>(VisibilityModifier maxVisibility) {
            bool hasDefault = HasDefaultConstructor(typeof (T), GetMoreVisibile(maxVisibility));
            if(hasDefault)
                throw new ContractAssertionException("Constructor more visible than expected");
        }

        /// <see cref="ITypeAsserter"/>
        public void DefaultConstructorHasMinVisibility<T>(VisibilityModifier minVisibility) {
            bool hasDefault = HasDefaultConstructor(typeof(T), GetLessVisibile(minVisibility));
            if (hasDefault)
                throw new ContractAssertionException("Constructor less visible than expected");
        }

        /// <see cref="ITypeAsserter"/>
        public void IsPublic<T>() {
            VisibilityModifier visibility = GetVisibilityModifier(typeof(T));
            if (visibility != VisibilityModifier.Public)
                throw new ContractAssertionException(VisibilityModifier.Public, visibility);
        }

        /// <see cref="ITypeAsserter"/>
        public void IsInternal<T>() {
            VisibilityModifier visibility = GetVisibilityModifier(typeof(T));
            if (visibility != VisibilityModifier.Internal)
                throw new ContractAssertionException(VisibilityModifier.Public, visibility);
        }

        /// <see cref="ITypeAsserter"/>
        public void IsAbstract<T>() {
            if(!typeof(T).IsAbstract)
                throw new ContractAssertionException("Expected abstract type");
        }

        private static VisibilityModifier GetVisibilityModifier(Type t) {
            if (t.IsPublic)
                return VisibilityModifier.Public;
            if (!t.IsVisible)
                return VisibilityModifier.Internal;
            return VisibilityModifier.Private;
        }

        private static TypeType GetTypeType(Type t) {
            if (t.IsInterface)
                return TypeType.Interface;
            if (t.IsValueType && !t.IsEnum)
                return TypeType.Structure;
            if (t.IsEnum)
                return TypeType.Enumeration;
            return TypeType.Class;
        }

        private static bool HasDefaultConstructor(Type t, params VisibilityModifier[] allowedVisibilities) {
            ConstructorInfo[] constructors =
                t.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
            bool hasDefault = false;
            foreach (ConstructorInfo constructor in constructors) {
                VisibilityModifier constructorVisibility = GetConstructorVisibility(constructor);
                bool allowed = false;
                foreach(VisibilityModifier allowedVisibility in allowedVisibilities) {
                    if(allowedVisibility == constructorVisibility)
                        allowed = true;
                }
                if(!allowed)
                    continue;
                ParameterInfo[] parameters = constructor.GetParameters();
                if (parameters.Length == 0) {
                    hasDefault = true;
                }
            }
            return hasDefault;
        }

        private static VisibilityModifier GetConstructorVisibility(ConstructorInfo constructor) {
            if (constructor.IsPublic)
                return VisibilityModifier.Public;
            if (constructor.IsPrivate)
                return VisibilityModifier.Private;
            if (constructor.IsFamily)
                return VisibilityModifier.Protected;
            if (constructor.IsAssembly)
                return VisibilityModifier.Internal;
            return VisibilityModifier.Public;   //???
        }

        private static VisibilityModifier[] GetMoreVisibile(VisibilityModifier minVisibility) {
            List<VisibilityModifier> result = new List<VisibilityModifier>();
            switch(minVisibility) {
                case VisibilityModifier.Private:
                    result.Add(VisibilityModifier.Protected);
                    result.Add(VisibilityModifier.Internal);
                    result.Add(VisibilityModifier.Public);
                    break;
                case VisibilityModifier.Protected:
                    result.Add(VisibilityModifier.Internal);
                    result.Add(VisibilityModifier.Public);
                    break;
                case VisibilityModifier.Internal:
                    result.Add(VisibilityModifier.Public);
                    break;
            }
            return result.ToArray();
        }

        private static VisibilityModifier[] GetLessVisibile(VisibilityModifier maxVisibility) {
            List<VisibilityModifier> result = new List<VisibilityModifier>();
            switch (maxVisibility) {
                case VisibilityModifier.Public:
                    result.Add(VisibilityModifier.Protected);
                    result.Add(VisibilityModifier.Internal);
                    result.Add(VisibilityModifier.Private);
                    break;
                case VisibilityModifier.Internal:
                    result.Add(VisibilityModifier.Protected);
                    result.Add(VisibilityModifier.Private);
                    break;
                case VisibilityModifier.Protected:
                    result.Add(VisibilityModifier.Private);
                    break;
            }
            return result.ToArray();
        }
    }
}
